--- /dev/null
+/*
+ DeLorme PN-20/40 USB "DeLBin" protocol
+
+ Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com
+ Copyright (C) 2005 Robert Lipe, robertlipe@usa.net
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+ */
+
+#include "defs.h"
+#include <assert.h>
+
+#define MYNAME "delbin"
+
+/*
+Device documentation:
+"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf
+obtained here: http://forum.delorme.com/viewtopic.php?t=13846
+
+Notes:
+Initial development was done with a PN-40, firmware 2.4.123299. The test
+device was upgraded to firmware 2.5.165506 during development.
+
+The "data size" in the message header includes the 4 trailer bytes, so it
+is really the size of the whole message minus the header.
+
+The time before an unacknowledged message will be retransmitted by the
+device is on the order of 2 to 4 seconds.
+
+Retrieving all tracks at once (using code 0 in message 0xb031) does not
+seem to work, it hangs after the first track, maybe waiting for some
+undocumented response message.
+
+Character encoding is not documented, appears to be 8859-1.
+
+The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the
+"reserved" byte in message 0xb012 were discovered by examining the data
+transferred between the device and DeLorme Topo 8.0. They may have been
+added in the PN-40 2.5 firmware.
+*/
+
+//-----------------------------------------------------------------------------
+// interface to platform-specific device I/O
+typedef struct {
+ void (*init)(const char* name);
+ void (*deinit)(void);
+ unsigned (*packet_read)(void*);
+ unsigned (*packet_write)(const void*, unsigned);
+} delbin_os_ops_t;
+
+// really static, only extern so it can be forward declared
+extern delbin_os_ops_t delbin_os_ops;
+
+static unsigned delbin_os_packet_size;
+//-----------------------------------------------------------------------------
+
+// number of times to attempt a transfer before giving up
+#define ATTEMPT_MAX 2
+
+// debug output: low, medium, high
+#define DBGLVL_L 1
+#define DBGLVL_M 2
+#define DBGLVL_H 3
+
+#define UNKNOWN_ELEV -2000000
+
+#define sizeofarray(x) (sizeof(x) / sizeof(x[0]))
+
+static char* opt_getposn;
+static char* opt_logs;
+static char* opt_long_notes;
+
+static arglist_t delbin_args[] = {
+ { "get_posn", &opt_getposn, "Return current position as a waypoint",
+ NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
+ { "logs", &opt_logs, "Include groundspeak logs when writing",
+ NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
+ { "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version",
+ NULL, ARGTYPE_BOOL, ARG_NOMINMAX },
+ ARG_TERMINATOR
+};
+
+// Whether device understands message 0xb016
+static int use_extended_notes;
+
+static const char* waypoint_symbol(unsigned index);
+static unsigned waypoint_symbol_index(const char* name);
+static int track_color(unsigned index);
+static unsigned track_color_index(int bgr);
+
+static unsigned waypoint_i;
+static unsigned waypoint_n;
+static waypoint** wp_array;
+
+//-----------------------------------------------------------------------------
+// Message ids and sizes. Only the needed ones are here.
+// Note that "in" and "out" ids are named as in the device documentation,
+// so "in" means to the device, "out" means from.
+#define MSG_ACK 0xaa00
+#define MSG_BREAK 0xaa02
+#define MSG_BREAK_SIZE 33
+#define MSG_NAVIGATION 0xa010
+#define MSG_REQUEST_ROUTES 0xb051
+#define MSG_REQUEST_ROUTES_SIZE 65
+#define MSG_REQUEST_TRACKS 0xb031
+#define MSG_REQUEST_TRACKS_SIZE 33
+#define MSG_REQUEST_WAYPOINTS 0xb012
+#define MSG_REQUEST_WAYPOINTS_SIZE 15
+#define MSG_ROUTE_COUNT 0xb050
+#define MSG_ROUTE_HEADER_IN 0xb055
+#define MSG_ROUTE_HEADER_OUT 0xb052
+#define MSG_ROUTE_POINT_IN 0xb056
+#define MSG_ROUTE_POINT_OUT 0xb053
+#define MSG_ROUTE_SHAPE_IN 0xb057
+#define MSG_ROUTE_SHAPE_OUT 0xb054
+#define MSG_SATELLITE_INFO 0xa020
+#define MSG_TRACK_COUNT 0xb030
+#define MSG_TRACK_HEADER_IN 0xb035
+#define MSG_TRACK_HEADER_OUT 0xb032
+#define MSG_TRACK_POINT_IN 0xb036
+#define MSG_TRACK_POINT_OUT 0xb033
+#define MSG_TRANSFER_COMPLETE 0xaa04
+#define MSG_VERSION 0xa001
+#define MSG_WAYPOINT_COUNT 0xb010
+#define MSG_WAYPOINT_IN 0xb014
+#define MSG_WAYPOINT_OUT 0xb013
+// Undocumented:
+// This one looks like MSG_ACK, except it also has a string in it that says
+// something like "device is busy". The expected MSG_ACK usually immediately
+// follows it, so the point of this one is unclear.
+#define MSG_NACK 0xaa01
+// Long waypoint notes
+#define MSG_WAYPOINT_NOTE_IN 0xb016
+#define MSG_WAYPOINT_NOTE_OUT 0xb015
+
+//-----------------------------------------------------------------------------
+// Message structures
+
+// Output Waypoint Message
+// Message ID: 0xB013
+// Input Waypoint Message
+// Message ID: 0xB014
+typedef struct {
+ gbuint8 total[4]; // U32
+ gbuint8 index[4]; // U32
+ gbuint8 year;
+ gbuint8 month;
+ gbuint8 day;
+ gbuint8 hour;
+ gbuint8 minute;
+ gbuint8 second;
+ gbuint8 latitude[4]; // S32 rad * 100000000
+ gbuint8 longitude[4]; // S32 rad * 100000000
+ gbuint8 elevation[4]; // F32 meters
+ gbuint8 color;
+ gbuint8 symbol;
+ gbuint8 name_size;
+ char name[1];
+ // note_size[2] U16
+ // note[note_size]
+} msg_waypoint_t;
+
+// undocumented, seen with PN-40 2.5 firmware
+// output waypoint note
+// Message ID: 0xB015
+// input waypoint note
+// Message ID: 0xB016
+typedef struct {
+ gbuint8 index[2];
+ gbuint8 total[2];
+ gbuint8 name_size;
+ char name[1];
+ // note_size[2]
+ // note[note_size]
+} msg_waypoint_note_t;
+
+// Output Track Point Message
+// Message ID: 0xB033
+// Input Track Point Message
+// Message ID: 0xB036
+typedef struct {
+ gbuint8 total[4]; // U32
+ gbuint8 index[4]; // U32
+ gbuint8 number;
+ struct {
+ gbuint8 year;
+ gbuint8 month;
+ gbuint8 day;
+ gbuint8 hour;
+ gbuint8 minute;
+ gbuint8 second;
+ gbuint8 latitude[4]; // S32 rad * 100000000
+ gbuint8 longitude[4]; // S32 rad * 100000000
+ gbuint8 elevation[4]; // F32 meters
+ gbuint8 speed[2]; // U16 km/h * 10
+ gbuint8 heading[2]; // U16 deg * 100
+ gbuint8 status;
+ } point[1];
+} msg_track_point_t;
+
+// Output Track Header (Name) Message
+// Message ID: 0xB032
+typedef struct {
+ gbuint8 total_tracks[2]; // U16
+ gbuint8 number[2]; // U16
+ char name[32];
+ gbuint8 total_points[4]; // U32
+ gbuint8 year;
+ gbuint8 month;
+ gbuint8 day;
+ gbuint8 hour;
+ gbuint8 minute;
+ gbuint8 second;
+ gbuint8 color[2]; // U16
+ gbuint8 distance[4]; // U32 m
+ gbuint8 duration[4]; // U32 sec
+ gbuint8 comment_size[2]; // U16
+ char comment[1];
+} msg_track_header_t;
+
+// Input Upload Track Header Message
+// Message ID: 0xB035
+typedef struct {
+ char name[32];
+ gbuint8 total_points[4]; // U32
+ gbuint8 year;
+ gbuint8 month;
+ gbuint8 day;
+ gbuint8 hour;
+ gbuint8 minute;
+ gbuint8 second;
+ gbuint8 color[2]; // U16
+ gbuint8 comment_size[2]; // U16
+ char comment[1];
+} msg_track_header_in_t;
+
+// Output Route Shape Message
+// Message ID: 0xB054
+typedef struct {
+ gbuint8 total[4]; // U32
+ gbuint8 index[4]; // U32
+ gbuint8 number;
+ gbuint8 reserved;
+ struct {
+ gbuint8 latitude[4]; // S32 rad * 100000000
+ gbuint8 longitude[4]; // S32 rad * 100000000
+ } point[1];
+} msg_route_shape_t;
+
+// Output Route Point Message
+// Message ID: 0xB053
+// Input Route Itin Point Message
+// Message ID: 0xB056
+typedef struct {
+ gbuint8 total[4]; // U32
+ gbuint8 index[4]; // U32
+ char name[32];
+ gbuint8 latitude[4]; // S32 rad * 100000000
+ gbuint8 longitude[4]; // S32 rad * 100000000
+ gbuint8 time_from_start[4]; // U32 sec
+ gbuint8 distance_from_start[4]; // F32 km
+ gbuint8 bearing_in[2]; // U16 deg * 100
+ gbuint8 bearing_out[2]; // U16 deg * 100
+ gbuint8 bearing_next[2]; // U16 deg * 100
+ gbuint8 itinerary_type;
+ gbuint8 turn_type;
+ gbuint8 road_class[2]; // U16
+ gbuint8 feature_code[4]; // U32
+ gbuint8 exit_label_size;
+ char exit_label[1];
+ // comment_size U8
+ // comment[comment_size]
+ // shape_pt_count U32
+} msg_route_point_t;
+
+// Output Route Header (Name) Message
+// Message ID: 0xB052
+typedef struct {
+ gbuint8 total[2]; // U16
+ gbuint8 index[2]; // U16
+ char name[64];
+ gbuint8 type;
+ gbuint8 total_route_point[4]; // U32
+ gbuint8 total_shape_point[4]; // U32
+} msg_route_header_t;
+
+// Input Upload Route Header Message
+// Message ID: 0xB055
+typedef struct {
+ char name[64];
+ gbuint8 type;
+ gbuint8 total_route_point[4]; // U32
+ gbuint8 total_shape_point[4]; // U32
+} msg_route_header_in_t;
+
+// Output Navigation Message
+// Message ID: 0xA010
+typedef struct {
+ gbuint8 gps_week[2]; // U16
+ gbuint8 time_of_week[8]; // D64 sec
+ gbuint8 year[2]; // U16
+ gbuint8 month;
+ gbuint8 day;
+ gbuint8 hour;
+ gbuint8 minute;
+ gbuint8 second;
+ gbuint8 satellites;
+ gbuint8 latitude[8]; // D64 deg
+ gbuint8 longitude[8]; // D64 deg
+ gbuint8 elevation[8]; // D64 meters
+ gbuint8 geoid_offset[2]; // S16 meters * 10
+ gbuint8 speed[4]; // F32 km/h
+ gbuint8 heading[2]; // U16 deg * 100
+ gbuint8 magnetic_variation[2]; // S16 deg * 100
+ gbuint8 fix_status;
+} msg_navigation_t;
+
+// Output Satellite Info Message
+// Message ID: 0xA020
+typedef struct {
+ gbuint8 gps_week[2]; // U16
+ gbuint8 time_of_week[8]; // D64 sec
+ gbuint8 hdop[2]; // U16
+ gbuint8 vdop[2]; // U16
+ gbuint8 pdop[2]; // U16
+ gbuint8 number;
+ struct {
+ gbuint8 prn;
+ gbuint8 azimuth[2]; // S16 deg? * 100
+ gbuint8 elevation[2]; // S16 deg? * 100
+ gbuint8 Cn0[2]; // U16 snr * 100
+ gbuint8 status;
+ } sat[1];
+} msg_satellite_t;
+
+// Output Version Message
+// Message ID: 0xA001
+typedef struct {
+ gbuint8 firmware_version[4];
+ char company[32];
+ char product[32];
+ char firmware[32];
+ char gps_firmware[48];
+ char serial[16];
+ char extra[16];
+} msg_version_t;
+
+//-----------------------------------------------------------------------------
+
+static gbuint16
+checksum(const gbuint8* p, unsigned n)
+{
+ int x = 0;
+ unsigned i;
+ for (i = n / 2; i > 0; i--) {
+ x += *p++;
+ x += *p++ << 8;
+ }
+ if (n & 1) {
+ x += *p;
+ }
+ return (gbuint16)-x;
+}
+
+//-----------------------------------------------------------------------------
+// OS packet read/write wrappers
+
+static unsigned
+packet_read(void* buf)
+{
+ unsigned n = delbin_os_ops.packet_read(buf);
+ if (n == 0) {
+ fatal(MYNAME ": read 0\n");
+ }
+ if (global_opts.debug_level >= DBGLVL_H) {
+ unsigned j;
+ warning(MYNAME ": pcktrd ");
+ for (j = 0; j < n; j++) {
+ warning("%02x ", ((gbuint8*)buf)[j]);
+ }
+ warning("\n");
+ }
+ return n;
+}
+
+static void
+packet_write(const void* p, unsigned size)
+{
+ unsigned n;
+ if (global_opts.debug_level >= DBGLVL_H) {
+ unsigned j;
+ warning(MYNAME ": pcktwr ");
+ for (j = 0; j < size; j++) {
+ warning("%02x ", ((gbuint8*)p)[j]);
+ }
+ warning("\n");
+ }
+ n = delbin_os_ops.packet_write(p, size);
+ if (n != size) {
+ fatal(MYNAME ": short write %u %u\n", size, n);
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+// dynamically sized buffer with space reserved for message header and trailer
+typedef struct {
+ // message data size
+ unsigned size;
+ // buffer size
+ unsigned capacity;
+ gbuint8* buf;
+ // convenience pointer to message data area
+ void* data;
+} message_t;
+
+static void
+message_init(message_t* m)
+{
+ m->capacity = 100;
+ m->buf = xmalloc(m->capacity);
+ m->data = m->buf + 2 + 8;
+}
+
+static void
+message_init_size(message_t* m, unsigned size)
+{
+ m->size = size;
+ m->capacity = 2 + 8 + size + 4;
+ m->buf = xmalloc(m->capacity);
+ m->data = m->buf + 2 + 8;
+}
+
+static void
+message_free(message_t* m)
+{
+ xfree(m->buf);
+ m->buf = NULL;
+ m->data = NULL;
+}
+
+static void
+message_ensure_size(message_t* m, unsigned size)
+{
+ m->size = size;
+ if (m->capacity < 2 + 8 + size + 4) {
+ m->capacity = 2 + 8 + size + 4;
+ xfree(m->buf);
+ m->buf = xmalloc(m->capacity);
+ m->data = m->buf + 2 + 8;
+ }
+}
+
+static unsigned
+message_get_id(const message_t* m)
+{
+ return le_readu16(m->buf + 4);
+}
+
+//-----------------------------------------------------------------------------
+
+static void
+message_write(unsigned msg_id, message_t* m)
+{
+ unsigned chksum;
+ unsigned count;
+ unsigned n;
+ gbuint8* p = m->buf;
+
+ // header (2 start bytes filled in later)
+ p[2] = 0xdb;
+ p[3] = 0xfe;
+ le_write16(p + 4, msg_id);
+ // "data size" includes 4 trailer bytes
+ le_write16(p + 6, m->size + 4);
+ chksum = checksum(p + 2, 6);
+ le_write16(p + 8, chksum);
+ // message data (filled in by caller)
+ chksum = checksum(m->data, m->size);
+ n = 2 + 8 + m->size;
+ // trailer (checksum and marker bytes)
+ le_write16(p + n, chksum);
+ p[n + 2] = 0xad;
+ p[n + 3] = 0xbc;
+ // size of message not counting packet start bytes
+ count = 8 + m->size + 4;
+ do {
+ const gbuint8 save0 = p[0];
+ const gbuint8 save1 = p[1];
+ n = delbin_os_packet_size - 2;
+ if (n > count) {
+ n = count;
+ }
+ // doc. says 0x20, device sends 0, probably ignored
+ p[0] = 0x20;
+ // valid bytes in packet after first 2
+ p[1] = n;
+ packet_write(p, 2 + n);
+ p[0] = save0;
+ p[1] = save1;
+ p += n;
+ count -= n;
+ } while (count != 0);
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": sent %x\n", msg_id);
+}
+
+// Get one valid message.
+// If a corrupted message with the right id is seen, return failure (0).
+static unsigned
+message_read_1(unsigned msg_id, message_t* m)
+{
+ gbuint8 buf[256];
+ gbuint8* p;
+ unsigned total = 0;
+ unsigned count = 0;
+ unsigned id = 0;
+
+ for (;;) {
+ for (;;) {
+ unsigned n = packet_read(buf);
+ if (n >= 10 && buf[2] == 0xdb && buf[3] == 0xfe &&
+ checksum(buf + 2, 6) == le_readu16(buf + 8))
+ {
+ count = buf[1] - 8;
+ total = le_readu16(buf + 6);
+ id = le_readu16(buf + 4);
+ message_ensure_size(m, total - 4);
+ memcpy(m->buf, buf, 2 + buf[1]);
+ break;
+ }
+ }
+ while (count < total && buf[1] == delbin_os_packet_size - 2) {
+ unsigned n;
+ packet_read(buf);
+ n = buf[1];
+ if (n > total - count) {
+ n = total - count;
+ }
+ memcpy((char*)m->data + count, buf + 2, n);
+ count += n;
+ }
+ p = (gbuint8*)m->data + m->size;
+ if (checksum(m->data, m->size) == le_readu16(p) &&
+ p[2] == 0xad && p[3] == 0xbc)
+ {
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": received %x\n", id);
+ break;
+ }
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": corrupted message %x\n", id);
+ if (id == msg_id) {
+ id = 0;
+ break;
+ }
+ }
+ return id;
+}
+
+// Send MSG_ACK for given message
+static void
+message_ack(unsigned id, const message_t* m)
+{
+ message_t ack;
+ char* p1;
+ const char* p2 = m->data;
+ switch (id) {
+ case MSG_ACK:
+ case MSG_NACK:
+ case MSG_NAVIGATION:
+ case MSG_SATELLITE_INFO:
+ // don't ack these
+ return;
+ }
+ message_init_size(&ack, 4);
+ p1 = ack.data;
+ // ack payload is id and body checksum of acked message
+ le_write16(p1, id);
+ p1[2] = p2[m->size];
+ p1[3] = p2[m->size + 1];
+ message_write(MSG_ACK, &ack);
+ message_free(&ack);
+}
+
+// Get specific message, ignoring others. Sends ACK for non-interval messages.
+// Gives up if 6 navigation messages are received, which means we waited at least
+// 5 seconds.
+static int
+message_read(unsigned msg_id, message_t* m)
+{
+ unsigned id;
+ int interval_message_count = 0;
+
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": looking for %x\n", msg_id);
+ for (;;) {
+ id = message_read_1(msg_id, m);
+ if (id == 0) {
+ break;
+ }
+ message_ack(id, m);
+ if (id == msg_id) {
+ break;
+ }
+ if (id == MSG_NAVIGATION) {
+ interval_message_count++;
+ if (interval_message_count == 6) {
+ break;
+ }
+ }
+ }
+ return id == msg_id;
+}
+
+// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE
+static int
+get_batch(message_t** array, unsigned* n)
+{
+ int success = 1;
+ unsigned array_max = 100;
+ message_t* a = xmalloc(array_max * sizeof(message_t));
+ unsigned i = 0;
+ unsigned id;
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": begin get_batch\n");
+ do {
+ unsigned timeout_count = 0;
+ if (i == array_max) {
+ message_t* old_a = a;
+ array_max += array_max;
+ a = xmalloc(array_max * sizeof(message_t));
+ memcpy(a, old_a, i * sizeof(message_t));
+ xfree(old_a);
+ }
+ message_init(&a[i]);
+ for (;;) {
+ id = message_read_1(0, &a[i]);
+ switch (id) {
+ case MSG_NAVIGATION:
+ timeout_count++;
+ if (timeout_count == 6) {
+ success = 0;
+ break;
+ }
+ // fall through
+ case MSG_ACK:
+ case MSG_NACK:
+ case MSG_SATELLITE_INFO:
+ continue;
+ }
+ break;
+ }
+ message_ack(id, &a[i]);
+ i++;
+ } while (success && id != MSG_TRANSFER_COMPLETE);
+ if (success) {
+ *array = a;
+ *n = i - 1;
+ message_free(&a[*n]);
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": end get_batch, %u messages\n", *n);
+ } else {
+ while (i--) {
+ message_free(&a[i]);
+ }
+ xfree(a);
+ *array = NULL;
+ *n = 0;
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": end get_batch, failed\n");
+ }
+ return success;
+}
+
+static struct {
+ unsigned msg_id;
+ message_t msg;
+} *batch_array;
+static unsigned batch_array_max;
+static unsigned batch_array_i;
+
+// add a message to sequence that will later be sent all at once
+static void
+add_to_batch(unsigned id, message_t* m)
+{
+ if (batch_array_i == batch_array_max) {
+ void* old = batch_array;
+ if (batch_array_max == 0) {
+ batch_array_max = 50;
+ }
+ batch_array_max += batch_array_max;
+ batch_array = xmalloc(batch_array_max * sizeof(*batch_array));
+ if (batch_array_i) {
+ memcpy(batch_array, old, batch_array_i * sizeof(*batch_array));
+ xfree(old);
+ }
+ }
+ batch_array[batch_array_i].msg_id = id;
+ batch_array[batch_array_i].msg = *m;
+ batch_array_i++;
+ memset(m, 0, sizeof(*m));
+}
+
+// send an accumulated sequence of messages
+static void
+send_batch(int expect_transfer_complete)
+{
+ message_t m;
+ const unsigned n = batch_array_i;
+ unsigned i;
+
+ message_init(&m);
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": begin send_batch, %u messages\n", n);
+ for (i = 0; i < n; i++) {
+ unsigned timeout_count = 0;
+ int nack = 0;
+ message_write(batch_array[i].msg_id, &batch_array[i].msg);
+ for (;;) {
+ unsigned id = message_read_1(0, &m);
+ switch (id) {
+ case MSG_ACK:
+ if (nack) gb_sleep(100000);
+ break;
+ case MSG_NAVIGATION:
+ timeout_count++;
+ if (timeout_count > 2) {
+ fatal(MYNAME ": send_batch timed out\n");
+ }
+ if (timeout_count == 2) {
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id);
+ message_write(batch_array[i].msg_id, &batch_array[i].msg);
+ }
+ // fall through
+ case MSG_NACK:
+ case MSG_SATELLITE_INFO:
+ continue;
+ default:
+ warning(MYNAME ": unexpected response message %x during send_batch\n", id);
+ continue;
+ }
+ break;
+ }
+ }
+ if (expect_transfer_complete) {
+ message_read(MSG_TRANSFER_COMPLETE, &m);
+ }
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": end send_batch\n");
+ for (i = n; i--;) {
+ message_free(&batch_array[i].msg);
+ }
+ xfree(batch_array);
+ message_free(&m);
+ batch_array_i = batch_array_max = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Coordinate conversion
+
+static double
+delbin_rad2deg(gbint32 x)
+{
+ return x * ((180 / M_PI) / 100000000);
+}
+
+static gbint32
+delbin_deg2rad(double x)
+{
+ return (gbint32)(x * ((M_PI / 180) * 100000000));
+}
+
+//-----------------------------------------------------------------------------
+// Waypoint reading
+
+static time_t
+decode_time(const gbuint8* p)
+{
+ struct tm t;
+ t.tm_year = p[0];
+ t.tm_mon = p[1] - 1;
+ t.tm_mday = p[2];
+ t.tm_hour = p[3];
+ t.tm_min = p[4];
+ t.tm_sec = p[5];
+ return mkgmtime(&t);
+}
+
+static waypoint*
+decode_waypoint(const void* data)
+{
+ waypoint* wp = waypt_new();
+ const msg_waypoint_t* p = data;
+ const char* s;
+ float f;
+
+ wp->creation_time = decode_time(&p->year);
+ wp->latitude = delbin_rad2deg(le_read32(p->latitude));
+ wp->longitude = delbin_rad2deg(le_read32(p->longitude));
+ f = le_read_float(p->elevation);
+ if (f > UNKNOWN_ELEV) {
+ wp->altitude = f;
+ }
+ wp->icon_descr = waypoint_symbol(p->symbol);
+ if (wp->icon_descr) {
+ wp->icon_descr = xstrdup(wp->icon_descr);
+ }
+ if (p->name_size && p->name[0]) {
+ wp->description = xstrdup(p->name);
+ }
+ s = p->name + p->name_size;
+ if (le_readu16(s) && s[2]) {
+ wp->notes = xstrdup(s + 2);
+ }
+ return wp;
+}
+
+static void
+read_waypoints(void)
+{
+ message_t m;
+ message_t* msg_array;
+ unsigned msg_array_n;
+ waypoint* wp = NULL;
+ unsigned n_point;
+ unsigned notes_i = 0;
+ unsigned notes_max = 0;
+ unsigned i;
+ int attempt = ATTEMPT_MAX;
+
+ message_init(&m);
+ // get number of waypoints
+ for (;;) {
+ m.size = 0;
+ message_write(MSG_WAYPOINT_COUNT, &m);
+ if (message_read(MSG_WAYPOINT_COUNT, &m))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading waypoint count failed\n");
+ }
+ n_point = le_readu32(m.data);
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": %u waypoints\n", n_point);
+ if (n_point == 0) {
+ message_free(&m);
+ return;
+ }
+ // get waypoint messages
+ attempt = ATTEMPT_MAX;
+ for (;;) {
+ m.size = MSG_REQUEST_WAYPOINTS_SIZE;
+ memset(m.data, 0, m.size);
+ // This byte is documented as reserved. Setting it to 3 is required to get
+ // extended notes (message 0xb015) with PN-40 firmware 2.5.
+ // Whether it has any effect with earlier firmware or the PN-20 is unknown.
+ ((char*)m.data)[1] = 3;
+ message_write(MSG_REQUEST_WAYPOINTS, &m);
+ if (get_batch(&msg_array, &msg_array_n))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading waypoints failed\n");
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": timed out reading waypoints, retrying\n");
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ }
+ message_free(&m);
+ // process waypoint messages
+ for (i = 0; i < msg_array_n; i++) {
+ unsigned id = message_get_id(&msg_array[i]);
+ if (id == MSG_WAYPOINT_OUT) {
+ wp = decode_waypoint(msg_array[i].data);
+ waypt_add(wp);
+ notes_i = 0;
+ notes_max = 0;
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": read waypoint '%s'\n", wp->description);
+ } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) {
+ const msg_waypoint_note_t* p = msg_array[i].data;
+ const char* s = p->name + p->name_size;
+ unsigned nn = le_readu16(s);
+ if (notes_max < notes_i + nn) {
+ char* old = wp->notes;
+ if (notes_max == 0) {
+ notes_max = nn;
+ }
+ do {
+ notes_max += notes_max;
+ } while (notes_max < notes_i + nn);
+ wp->notes = xmalloc(notes_max);
+ if (old) {
+ memcpy(wp->notes, old, notes_i);
+ xfree(old);
+ }
+ }
+ if (nn) {
+ memcpy(wp->notes + notes_i, s + 2, nn);
+ notes_i += nn;
+ if (wp->notes[notes_i - 1] == 0) {
+ notes_i--;
+ }
+ }
+ } else {
+ fatal(MYNAME ": unexpected message %x while reading waypoints\n", id);
+ }
+ message_free(&msg_array[i]);
+ }
+ xfree(msg_array);
+}
+
+//-----------------------------------------------------------------------------
+// Waypoint writing
+
+static void
+encode_time(time_t time_, gbuint8* p)
+{
+ const struct tm* t = gmtime(&time_);
+ p[0] = t->tm_year;
+ p[1] = t->tm_mon + 1;
+ p[2] = t->tm_mday;
+ p[3] = t->tm_hour;
+ p[4] = t->tm_min;
+ p[5] = t->tm_sec;
+}
+
+static void
+get_gc_notes(const waypoint* wp, int* symbol, char** notes, unsigned* notes_size)
+{
+ fs_xml* fs_gpx;
+ xml_tag* root = NULL;
+ gbfile* fd = gbfopen(NULL, "w", MYNAME);
+ const char* size = NULL;
+ int gc_sym = 0;
+
+ switch (wp->gc_data->type) {
+ case gt_traditional: gc_sym = 160; break;
+ case gt_multi: gc_sym = 161; break;
+ case gt_virtual: gc_sym = 169; break;
+ case gt_letterbox: gc_sym = 163; break;
+ case gt_event: gc_sym = 165; break;
+ case gt_suprise: gc_sym = 162; break;
+ case gt_webcam: gc_sym = 170; break;
+ case gt_earth: gc_sym = 168; break;
+ case gt_benchmark: gc_sym = 172; break;
+ case gt_cito: gc_sym = 167; break;
+ case gt_mega: gc_sym = 166; break;
+ case gt_unknown:
+ case gt_locationless:
+ case gt_ape:
+ break;
+ }
+ if (wp->description) {
+ gbfputs(wp->description, fd);
+ if (wp->gc_data->placer) {
+ gbfprintf(fd, " by %s", wp->gc_data->placer);
+ }
+ gbfputc('\n', fd);
+ }
+ gbfprintf(fd, "Cache ID: %s\n", wp->shortname);
+ if (gc_sym) {
+ gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym));
+ *symbol = gc_sym;
+ } else if (wp->icon_descr) {
+ gbfprintf(fd, "%s\n", wp->icon_descr);
+ }
+ switch (wp->gc_data->container) {
+ case gc_micro: size = "Micro"; break;
+ case gc_small: size = "Small"; break;
+ case gc_regular: size = "Regular"; break;
+ case gc_large: size = "Large"; break;
+ case gc_unknown:
+ case gc_other:
+ case gc_virtual:
+ break;
+ }
+ if (size) {
+ gbfprintf(fd, "SIZE: %s\n", size);
+ }
+ if (wp->gc_data->diff % 10) {
+ gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0);
+ } else {
+ gbfprintf(fd, "D%u", wp->gc_data->diff / 10);
+ }
+ if (wp->gc_data->terr % 10) {
+ gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0);
+ } else {
+ gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10);
+ }
+ if (wp->gc_data->hint) {
+ gbfprintf(fd, "HINT: %s\n", wp->gc_data->hint);
+ }
+ if (wp->gc_data->desc_short.utfstring || wp->gc_data->desc_long.utfstring) {
+ gbfputs("DESC: ", fd);
+ if (wp->gc_data->desc_short.utfstring) {
+ char* s1 = strip_html(&wp->gc_data->desc_short);
+ char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
+ gbfprintf(fd, "%s\n", s2);
+ xfree(s2);
+ xfree(s1);
+ }
+ if (wp->gc_data->desc_long.utfstring) {
+ char* s1 = strip_html(&wp->gc_data->desc_long);
+ char* s2 = cet_str_utf8_to_any(s1, global_opts.charset);
+ gbfputs(s2, fd);
+ xfree(s2);
+ xfree(s1);
+ }
+ }
+ fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX);
+ if (opt_logs && fs_gpx && fs_gpx->tag) {
+ root = xml_findfirst(fs_gpx->tag, "groundspeak:logs");
+ }
+ if (root) {
+ xml_tag* curlog = xml_findfirst(root, "groundspeak:log");
+ if (curlog) {
+ gbfputs("\nLOG:\n", fd);
+ }
+ for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) {
+ xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type");
+ if (logpart) {
+ gbfprintf(fd, "%s\n", logpart->cdata);
+ }
+ logpart = xml_findfirst(curlog, "groundspeak:date");
+ if (logpart) {
+ time_t logtime = xml_parse_time(logpart->cdata, NULL);
+ const struct tm* logtm = gmtime(&logtime);
+ gbfprintf(fd, "%d-%d-%d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday);
+ }
+ logpart = xml_findfirst(curlog, "groundspeak:finder");
+ if (logpart) {
+ char* s = cet_str_utf8_to_any(logpart->cdata, global_opts.charset);
+ gbfputs(s, fd);
+ xfree(s);
+ }
+ logpart = xml_findfirst(curlog, "groundspeak:text");
+ if (logpart) {
+ char* s = cet_str_utf8_to_any(logpart->cdata, global_opts.charset);
+ gbfprintf(fd, ", %s", s);
+ xfree(s);
+ }
+ gbfputc('\n', fd);
+ }
+ }
+ gbfputc(0, fd);
+ *notes_size = fd->memlen;
+ *notes = xmalloc(*notes_size);
+ memcpy(*notes, fd->handle.mem, *notes_size);
+ gbfclose(fd);
+}
+
+static void
+write_waypoint_notes(const char* notes, unsigned size, const char* name)
+{
+ message_t m;
+ const unsigned name_size = strlen(name) + 1;
+ const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20;
+ const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg;
+ unsigned i = 1;
+
+ do {
+ char* pp;
+ unsigned n = bytes_per_msg;
+ msg_waypoint_note_t* p;
+ message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg);
+ p = m.data;
+ le_write16(p->index, i++);
+ le_write16(p->total, msg_count);
+ p->name_size = name_size;
+ memcpy(p->name, name, p->name_size);
+ pp = p->name + p->name_size;
+ if (n > size) {
+ n = size;
+ }
+ le_write16(pp, n);
+ pp += 2;
+ memcpy(pp, notes, n);
+ pp += n;
+ if (*(pp - 1)) {
+ *pp++ = 0;
+ }
+ notes += n;
+ size -= n;
+ m.size = pp - (char*)p;
+ add_to_batch(MSG_WAYPOINT_NOTE_IN, &m);
+ } while (size != 0);
+}
+
+static void
+write_waypoint(const waypoint* wp)
+{
+ message_t m;
+ msg_waypoint_t* p;
+ const char* name = wp->shortname;
+ unsigned name_size;
+ char* notes;
+ unsigned notes_size = 0;
+ unsigned extended_notes_size = 0;
+ char* notes_freeable = NULL;
+ int symbol = -1;
+ float elev = UNKNOWN_ELEV;
+ char* pp;
+
+ if (waypt_empty_gc_data(wp)) {
+ notes = wp->notes;
+ if (notes == NULL && wp->description && strcmp(wp->shortname, wp->description)) {
+ notes = wp->description;
+ }
+ if (notes) {
+ notes_size = strlen(notes) + 1;
+ }
+ } else {
+ get_gc_notes(wp, &symbol, ¬es, ¬es_size);
+ notes_freeable = notes;
+ if (wp->description) {
+ name = wp->description;
+ }
+ }
+ if (notes_size > 800) {
+ if (use_extended_notes) {
+ extended_notes_size = notes_size;
+ notes_size = 1;
+ } else {
+ notes_size = 800;
+ }
+ }
+ name_size = strlen(name) + 1;
+ if (name_size > 255) {
+ name_size = 255;
+ }
+ message_init_size(&m, 31 + name_size + notes_size);
+ p = m.data;
+
+ waypoint_i++;
+ le_write32(p->total, waypoint_n);
+ le_write32(p->index, waypoint_i);
+ encode_time(wp->creation_time, &p->year);
+ le_write32(p->latitude, delbin_deg2rad(wp->latitude));
+ le_write32(p->longitude, delbin_deg2rad(wp->longitude));
+ if (wp->altitude > unknown_alt) {
+ elev = wp->altitude;
+ }
+ le_write_float(p->elevation, elev);
+ if (symbol < 0) {
+ symbol = 0;
+ if (wp->icon_descr) {
+ symbol = waypoint_symbol_index(wp->icon_descr);
+ }
+ }
+ p->symbol = symbol;
+ p->name_size = name_size;
+ memcpy(p->name, name, name_size - 1);
+ p->name[name_size - 1] = 0;
+ pp = p->name + name_size;
+ m.size = (pp + 2 + notes_size) - (char*)p;
+ if (extended_notes_size) {
+ le_write16(pp, 0xffff);
+ pp[2] = 0;
+ } else {
+ le_write16(pp, notes_size);
+ if (notes) {
+ memcpy(pp + 2, notes, notes_size - 1);
+ pp[2 + notes_size - 1] = 0;
+ }
+ }
+
+ add_to_batch(MSG_WAYPOINT_IN, &m);
+
+ if (extended_notes_size) {
+ write_waypoint_notes(notes, extended_notes_size, name);
+ }
+ if (notes_freeable) {
+ xfree(notes_freeable);
+ }
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, name);
+}
+
+static void
+write_waypoints(void)
+{
+ waypoint_i = 0;
+ waypoint_n = waypt_count();
+ waypt_disp_all(write_waypoint);
+ send_batch(TRUE);
+}
+
+//-----------------------------------------------------------------------------
+// Track reading
+
+static void
+decode_sat_fix(waypoint* wp, const gbuint8 status)
+{
+ switch (status & 3) {
+ case 1: wp->fix = fix_none; break;
+ case 2: wp->fix = fix_2d; break;
+ case 3:
+ wp->fix = fix_3d;
+ if (status & 4) {
+ wp->fix = fix_dgps;
+ }
+ break;
+ }
+}
+
+static void
+decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point)
+{
+ const msg_track_point_t* p = data;
+ const unsigned n = p->number;
+ unsigned i;
+ unsigned j = *wp_array_i;
+
+ if (j + n > max_point) {
+ fatal(MYNAME ": read too many track points\n");
+ }
+ for (i = 0; i < n; i++, j++) {
+ waypoint* wp = waypt_new();
+ float elev = le_read_float(p->point[i].elevation);
+ wp_array[j] = wp;
+ wp->creation_time = decode_time(&p->point[i].year);
+ wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
+ wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
+ if (elev > UNKNOWN_ELEV) {
+ wp->altitude = elev;
+ }
+ wp->speed = le_readu16(p->point[i].speed);
+ wp->speed *= (100.0f / (60 * 60));
+ wp->wpt_flags.speed = 1;
+ decode_sat_fix(wp, p->point[i].status);
+ // use microseconds as track segment marker
+ wp->microseconds = p->point[i].status & 0x10;
+ }
+ *wp_array_i = j;
+}
+
+static void
+read_track(route_head* track)
+{
+ message_t m;
+ message_t* msg_array;
+ const msg_track_header_t* p;
+ unsigned msg_array_n;
+ unsigned wp_array_i = 0;
+ unsigned n_point;
+ unsigned segment = 1;
+ char* track_name = NULL;
+ unsigned i;
+ int attempt = ATTEMPT_MAX;
+
+ message_init(&m);
+ // read track messages
+ for (;;) {
+ m.size = MSG_REQUEST_TRACKS_SIZE;
+ memset(m.data, 0, m.size);
+ ((char*)m.data)[0] = 1; // Download single track
+ strcpy((char*)m.data + 1, track->rte_name);
+ message_write(MSG_REQUEST_TRACKS, &m);
+ if (get_batch(&msg_array, &msg_array_n))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading track '%s' failed\n", track->rte_name);
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": timed out reading track '%s', retrying\n", track->rte_name);
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ }
+ message_free(&m);
+ if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) {
+ fatal(MYNAME ": reading track '%s' failed (missing track header)\n", track->rte_name);
+ }
+ // process track messages
+ p = msg_array[0].data;
+ if (le_readu16(p->comment_size)) {
+ track->rte_desc = xstrdup(p->comment);
+ }
+ track->line_color.bbggrr = track_color(p->color[0]);
+ n_point = le_readu32(p->total_points);
+ wp_array = xcalloc(n_point, sizeof(*wp_array));
+ message_free(&msg_array[0]);
+ for (i = 1; i < msg_array_n; i++) {
+ unsigned id = message_get_id(&msg_array[i]);
+ if (id == MSG_TRACK_POINT_OUT) {
+ decode_track_point(msg_array[i].data, &wp_array_i, n_point);
+ } else {
+ fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, track->rte_name);
+ }
+ message_free(&msg_array[i]);
+ }
+ xfree(msg_array);
+ if (n_point != wp_array_i) {
+ fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i);
+ }
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": read track '%s' %u points\n", track->rte_name, n_point);
+ // make track (one for each segment)
+ track_add_wpt(track, wp_array[0]);
+ for (i = 1; i < n_point; i++) {
+ // if track segment marker
+ if (wp_array[i]->microseconds) {
+ const char* desc = track->rte_desc;
+ if (track_name == NULL) {
+ // save original name, append " #1" to first segment name
+ track_name = track->rte_name;
+ track->rte_name = xmalloc(strlen(track_name) + 4);
+ sprintf(track->rte_name, "%s #1", track_name);
+ }
+ // make a new track for segment
+ segment++;
+ track_add_head(track);
+ track = route_head_alloc();
+ track->rte_name = xmalloc(strlen(track_name) + 7);
+ sprintf(track->rte_name, "%s #%u", track_name, segment);
+ track->rte_desc = xstrdup(desc);
+ }
+ wp_array[i]->microseconds = 0;
+ track_add_wpt(track, wp_array[i]);
+ }
+ track_add_head(track);
+ if (track_name) {
+ xfree(track_name);
+ }
+ xfree(wp_array);
+}
+
+static void
+read_tracks(void)
+{
+ message_t m;
+ message_t* msg_array;
+ unsigned msg_array_n;
+ route_head** track_array;
+ unsigned total;
+ unsigned i;
+ int attempt = ATTEMPT_MAX;
+
+ message_init(&m);
+ // get number of tracks
+ for (;;) {
+ m.size = 0;
+ message_write(MSG_TRACK_COUNT, &m);
+ if (message_read(MSG_TRACK_COUNT, &m))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading track count failed\n");
+ }
+ total = le_readu32(m.data);
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": %u tracks\n", total);
+ if (total == 0) {
+ message_free(&m);
+ return;
+ }
+
+ // First get track headers, then request each track with non-zero number of points
+ attempt = ATTEMPT_MAX;
+ for (;;) {
+ m.size = MSG_REQUEST_TRACKS_SIZE;
+ memset(m.data, 0, m.size);
+ ((char*)m.data)[0] = 2; // Download all track headers
+ message_write(MSG_REQUEST_TRACKS, &m);
+ if (get_batch(&msg_array, &msg_array_n))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading track headers failed\n");
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": timed out reading track headers, retrying\n");
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ }
+ message_free(&m);
+ track_array = xcalloc(total, sizeof(*track_array));
+ for (i = 0; i < msg_array_n; i++) {
+ unsigned id = message_get_id(&msg_array[i]);
+ if (id == MSG_TRACK_HEADER_OUT) {
+ const msg_track_header_t* p = msg_array[i].data;
+ if (le_readu32(p->total_points)) {
+ track_array[i] = route_head_alloc();
+ track_array[i]->rte_name = xstrdup(p->name);
+ }
+ } else {
+ fatal(MYNAME ": unexpected message %x while reading track headers\n", id);
+ }
+ message_free(&msg_array[i]);
+ }
+ xfree(msg_array);
+ // get each track
+ for (i = 0; i < total; i++) {
+ if (track_array[i]) {
+ read_track(track_array[i]);
+ }
+ }
+ xfree(track_array);
+}
+
+//-----------------------------------------------------------------------------
+// Track writing
+
+static void
+write_track_points(void)
+{
+ message_t m;
+ const unsigned pt_per_msg = 10;
+ msg_track_point_t* p = NULL;
+ unsigned i = 0;
+ unsigned j = 0;
+
+ do {
+ const waypoint* wp = wp_array[i];
+ float f;
+
+ if (j == 0) {
+ message_init_size(&m, 9 + 23 * pt_per_msg);
+ p = m.data;
+ le_write32(p->total, waypoint_n);
+ le_write32(p->index, i + 1);
+ }
+ assert(p);
+ encode_time(wp->creation_time, &p->point[j].year);
+ le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude));
+ le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude));
+ f = UNKNOWN_ELEV;
+ if (wp->altitude > unknown_alt) {
+ f = wp->altitude;
+ }
+ le_write_float(p->point[j].elevation, f);
+ f = WAYPT_GET(wp, speed, 0);
+ f *= (60 * 60) / 100;
+ le_write16(p->point[j].speed, (gbuint16)f);
+ f = WAYPT_GET(wp, course, 0);
+ f *= 100;
+ le_write16(p->point[j].heading, (gbuint16)f);
+ switch (wp->fix) {
+ default: p->point[j].status = 0; break;
+ case fix_none: p->point[j].status = 1; break;
+ case fix_2d: p->point[j].status = 2; break;
+ case fix_3d: p->point[j].status = 3; break;
+ case fix_dgps: p->point[j].status = 4 | 3; break;
+ }
+ i++;
+ j++;
+ if (j == pt_per_msg || i == waypoint_n) {
+ p->number = j;
+ m.size = 9 + 23 * j;
+ add_to_batch(MSG_TRACK_POINT_IN, &m);
+ j = 0;
+ }
+ } while (i < waypoint_n);
+}
+
+static void
+write_track_begin(const route_head* track)
+{
+ waypoint_i = 0;
+ waypoint_n = track->rte_waypt_ct;
+ if (waypoint_n) {
+ wp_array = xmalloc(waypoint_n * sizeof(*wp_array));
+ }
+}
+
+static void
+write_track_point(const waypoint* wp)
+{
+ wp_array[waypoint_i++] = (waypoint*)wp;
+}
+
+static void
+write_track_end(const route_head* track)
+{
+ message_t m;
+ msg_track_header_in_t* p;
+ unsigned comment_size = 0;
+
+ if (waypoint_n == 0) {
+ return;
+ }
+ if (track->rte_desc) {
+ comment_size = strlen(track->rte_desc) + 1;
+ }
+ message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size);
+ p = m.data;
+ memset(p->name, 0, sizeof(p->name));
+ if (track->rte_name) {
+ strncpy(p->name, track->rte_name, sizeof(p->name) - 1);
+ } else {
+ sprintf(p->name, "%lu", (long)wp_array[0]->creation_time);
+ }
+ le_write32(p->total_points, waypoint_n);
+ encode_time(current_time(), &p->year);
+ le_write16(p->color, track_color_index(track->line_color.bbggrr));
+ le_write16(p->comment_size, comment_size);
+ if (comment_size) {
+ memcpy(p->comment, track->rte_desc, comment_size);
+ }
+ add_to_batch(MSG_TRACK_HEADER_IN, &m);
+ write_track_points();
+ send_batch(FALSE);
+ xfree(wp_array);
+}
+
+static void
+write_tracks(void)
+{
+ track_disp_all(write_track_begin, write_track_end, write_track_point);
+}
+
+//-----------------------------------------------------------------------------
+// Route reading
+
+static void
+decode_route_shape(const void* data, unsigned* wp_array_i)
+{
+ const msg_route_shape_t* p = data;
+ const unsigned n = p->number;
+ unsigned i;
+ unsigned j = *wp_array_i;
+
+ for (i = 0; i < n; i++, j++) {
+ char buf[32];
+ waypoint* wp = waypt_new();
+ wp_array[j] = wp;
+ wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude));
+ wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude));
+ sprintf(buf, "SHP%03u", j);
+ wp->shortname = xstrdup(buf);
+ }
+ *wp_array_i = j;
+}
+
+static waypoint*
+decode_route_point(const void* data)
+{
+ const msg_route_point_t* p = data;
+ const char* s = NULL;
+ gbfile* fd = gbfopen(NULL, "w", MYNAME);
+ waypoint* wp = waypt_new();
+ if (p->name[0]) {
+ wp->shortname = xstrdup(p->name);
+ }
+ // give these a higher priority than the shape points
+ wp->route_priority = 1;
+ wp->latitude = delbin_rad2deg(le_read32(p->latitude));
+ wp->longitude = delbin_rad2deg(le_read32(p->longitude));
+ switch (p->itinerary_type) {
+ case 1: s = "Start"; break;
+ case 2: s = "Stop"; break;
+ case 3: s = "Finish"; break;
+ case 4: s = "Via"; break;
+ case 5: s = "Via Hidden"; break;
+ case 6:
+ switch (p->turn_type) {
+ case 1: s = "Turn, Straight"; break;
+ case 2: s = "Turn, Right"; break;
+ case 3: s = "Turn, Bear Right"; break;
+ case 4: s = "Turn, Keep Right"; break;
+ case 5: s = "Turn, Left"; break;
+ case 6: s = "Turn, Bear Left"; break;
+ case 7: s = "Turn, Keep Left"; break;
+ case 8: s = "Turn, Reverse Direction"; break;
+ case 9: s = "Turn, Street Name Change"; break;
+ }
+ break;
+ }
+ if (s) {
+ gbfprintf(fd, "Type: %s", s);
+ }
+ if (p->exit_label_size && p->exit_label[0]) {
+ gbfprintf(fd, "\nExit: %s", p->exit_label);
+ }
+ s = p->exit_label + p->exit_label_size;
+ if (s[0] && s[1]) {
+ gbfprintf(fd, "\n%s", s + 1);
+ }
+ if (fd->memlen) {
+ gbfputc(0, fd);
+ wp->notes = xmalloc(fd->memlen);
+ memcpy(wp->notes, fd->handle.mem, fd->memlen);
+ }
+ gbfclose(fd);
+ return wp;
+}
+
+static void
+read_route(route_head* route)
+{
+ message_t m;
+ message_t* msg_array;
+ const msg_route_header_t* p;
+ unsigned msg_array_n;
+ unsigned wp_array_i = 0;
+ unsigned route_total, shape_total, total;
+ unsigned i;
+ int attempt = ATTEMPT_MAX;
+
+ message_init(&m);
+ for (;;) {
+ m.size = MSG_REQUEST_ROUTES_SIZE;
+ memset(m.data, 0, m.size);
+ ((char*)m.data)[0] = 1; // Download single route
+ strcpy((char*)m.data + 1, route->rte_name);
+ message_write(MSG_REQUEST_ROUTES, &m);
+ if (get_batch(&msg_array, &msg_array_n))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading route '%s' failed (timed out)\n", route->rte_name);
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": timed out reading route route '%s', retrying\n", route->rte_name);
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ }
+ message_free(&m);
+ if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) {
+ fatal(MYNAME ": missing route header\n");
+ }
+ p = msg_array[0].data;
+ route_total = le_readu32(p->total_route_point);
+ shape_total = le_readu32(p->total_shape_point);
+ total = route_total + shape_total;
+ wp_array = xcalloc(total, sizeof(*wp_array));
+ if (global_opts.debug_level >= DBGLVL_L) {
+ warning(MYNAME ": route '%s' %u points, %u shape points\n",
+ route->rte_name, route_total, shape_total);
+ }
+ message_free(&msg_array[0]);
+ for (i = 1; i < msg_array_n; i++) {
+ unsigned id = message_get_id(&msg_array[i]);
+ if (id == MSG_ROUTE_POINT_OUT) {
+ wp_array[wp_array_i] = decode_route_point(msg_array[i].data);
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": route point '%s'\n", wp_array[wp_array_i]->shortname);
+ wp_array_i++;
+ } else if (id == MSG_ROUTE_SHAPE_OUT) {
+ decode_route_shape(msg_array[i].data, &wp_array_i);
+ } else {
+ fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, route->rte_name);
+ }
+ message_free(&msg_array[i]);
+ }
+ xfree(msg_array);
+ if (total != wp_array_i) {
+ fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i);
+ }
+ for (i = 0; i < total; i++) {
+ route_add_wpt(route, wp_array[i]);
+ }
+ xfree(wp_array);
+ route_add_head(route);
+}
+
+static void
+read_routes(void)
+{
+ message_t m;
+ message_t* msg_array;
+ unsigned msg_array_n;
+ route_head** route_array;
+ unsigned total;
+ unsigned i;
+ int attempt = ATTEMPT_MAX;
+
+ message_init(&m);
+ // get number of routes
+ for (;;) {
+ m.size = 0;
+ message_write(MSG_ROUTE_COUNT, &m);
+ if (message_read(MSG_ROUTE_COUNT, &m))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading route count failed\n");
+ }
+ total = le_readu32(m.data);
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": %u routes\n", total);
+ if (total == 0) {
+ message_free(&m);
+ return;
+ }
+
+ // First get route headers, then request each route
+ attempt = ATTEMPT_MAX;
+ for (;;) {
+ m.size = MSG_REQUEST_ROUTES_SIZE;
+ memset(m.data, 0, m.size);
+ ((char*)m.data)[0] = 2; // Download all route headers
+ message_write(MSG_REQUEST_ROUTES, &m);
+ if (get_batch(&msg_array, &msg_array_n))
+ break;
+ if (--attempt == 0)
+ fatal(MYNAME ": reading route headers failed\n");
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": timed out reading route headers, retrying\n");
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ }
+ message_free(&m);
+ route_array = xcalloc(total, sizeof(*route_array));
+ for (i = 0; i < msg_array_n; i++) {
+ unsigned id = message_get_id(&msg_array[i]);
+ if (id == MSG_ROUTE_HEADER_OUT) {
+ route_array[i] = route_head_alloc();
+ route_array[i]->rte_name = xstrdup(((msg_route_header_t*)msg_array[i].data)->name);
+ } else {
+ fatal(MYNAME ": unexpected message %x while reading route headers\n", id);
+ }
+ message_free(&msg_array[i]);
+ }
+ xfree(msg_array);
+ // get each route
+ for (i = 0; i < total; i++) {
+ read_route(route_array[i]);
+ }
+ xfree(route_array);
+}
+
+//-----------------------------------------------------------------------------
+// Route writing
+
+static unsigned route_point_n;
+static unsigned shape_point_n;
+static unsigned* shape_point_counts;
+
+static void
+write_route_shape_points(waypoint** array, unsigned n)
+{
+ message_t m;
+ const unsigned pt_per_msg = 25;
+ msg_route_shape_t* p = NULL;
+ unsigned i = 0;
+ unsigned j = 0;
+
+ do {
+ if (j == 0) {
+ message_init_size(&m, 10 + 8 * pt_per_msg);
+ p = m.data;
+ le_write32(p->total, n);
+ le_write32(p->index, i + 1);
+ p->reserved = 0;
+ }
+ assert(p);
+ le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude));
+ le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude));
+ i++;
+ j++;
+ if (j == pt_per_msg || i == n) {
+ p->number = j;
+ m.size = 10 + 8 * j;
+ add_to_batch(MSG_ROUTE_SHAPE_IN, &m);
+ j = 0;
+ }
+ } while (i < n);
+}
+
+static void
+write_route_points(void)
+{
+ unsigned route_point_i = 0;
+ unsigned i = 0;
+
+ while (i < waypoint_n) {
+ message_t m;
+ unsigned shape_n;
+ const waypoint* wp = wp_array[i];
+ msg_route_point_t* p;
+ char* s;
+
+ message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4);
+ p = m.data;
+ memset(m.data, 0, m.size);
+ route_point_i++;
+ shape_n = shape_point_counts[route_point_i];
+ le_write32(p->total, route_point_n);
+ le_write32(p->index, route_point_i);
+ if (wp->shortname) {
+ strncpy(p->name, wp->shortname, sizeof(p->name) - 1);
+ } else {
+ sprintf(p->name, "RPT%u", route_point_i);
+ }
+ le_write32(p->latitude, delbin_deg2rad(wp->latitude));
+ le_write32(p->longitude, delbin_deg2rad(wp->longitude));
+ p->exit_label_size = 1;
+ s = p->exit_label + p->exit_label_size;
+ s[0] = 1; // comment size
+ le_write32(s + 2, shape_n);
+ if (route_point_i == 1) {
+ p->itinerary_type = 1; // start
+ } else if (route_point_i == route_point_n) {
+ p->itinerary_type = 3; // finish
+ }
+ add_to_batch(MSG_ROUTE_POINT_IN, &m);
+ i++;
+ if (shape_n) {
+ write_route_shape_points(&wp_array[i], shape_n);
+ i += shape_n;
+ }
+ }
+}
+
+static void
+write_route_begin(const route_head* track)
+{
+ waypoint_i = 0;
+ route_point_n = 0;
+ shape_point_n = 0;
+ waypoint_n = track->rte_waypt_ct;
+ if (waypoint_n) {
+ wp_array = xmalloc(waypoint_n * sizeof(*wp_array));
+ shape_point_counts = xcalloc(waypoint_n, sizeof(*shape_point_counts));
+ }
+}
+
+static void
+write_route_point(const waypoint* wp)
+{
+ const char* s = wp->shortname;
+ wp_array[waypoint_i++] = (waypoint*)wp;
+ if (s && s[0] == 'S' && s[1] == 'H' && s[2] == 'P' && s[3] >= '0' && s[3] <= '9') {
+ shape_point_n++;
+ shape_point_counts[route_point_n]++;
+ } else {
+ route_point_n++;
+ }
+}
+
+static void
+write_route_end(const route_head* route)
+{
+ message_t m;
+ msg_route_header_in_t* p;
+
+ if (waypoint_n == 0) {
+ return;
+ }
+ message_init_size(&m, sizeof(msg_route_header_in_t));
+ p = m.data;
+ memset(p->name, 0, sizeof(p->name));
+ if (route->rte_name) {
+ strncpy(p->name, route->rte_name, sizeof(p->name) - 1);
+ } else {
+ sprintf(p->name, "%lu", (long)wp_array[0]->creation_time);
+ }
+ p->type = 0;
+ le_write32(p->total_route_point, route_point_n);
+ le_write32(p->total_shape_point, shape_point_n);
+ add_to_batch(MSG_ROUTE_HEADER_IN, &m);
+ write_route_points();
+ send_batch(TRUE);
+ if (wp_array) {
+ xfree(wp_array);
+ xfree(shape_point_counts);
+ }
+}
+
+static void
+write_routes(void)
+{
+ route_disp_all(write_route_begin, write_route_end, write_route_point);
+}
+
+//-----------------------------------------------------------------------------
+// Current position
+
+static waypoint*
+decode_navmsg(const void* data)
+{
+ waypoint* wp = waypt_new();
+ const msg_navigation_t* p = data;
+ struct tm t;
+
+ t.tm_year = le_readu16(p->year) - 1900;
+ t.tm_mon = p->month - 1;
+ t.tm_mday = p->day;
+ t.tm_hour = p->hour;
+ t.tm_min = p->minute;
+ t.tm_sec = p->second;
+ wp->creation_time = mkgmtime(&t);
+ wp->sat = p->satellites;
+ wp->latitude = le_read_double(p->latitude);
+ wp->longitude = le_read_double(p->longitude);
+ wp->altitude = le_read_double(p->elevation);
+ wp->speed = le_read_float(p->speed);
+ wp->speed *= (1000.0f / (60 * 60));
+ wp->wpt_flags.speed = 1;
+ wp->course = le_readu16(p->heading);
+ wp->course /= 100;
+ wp->wpt_flags.course = 1;
+ decode_sat_fix(wp, p->fix_status);
+ wp->shortname = xstrdup("Position");
+ return wp;
+}
+
+static waypoint*
+read_position(void)
+{
+ waypoint* wp;
+ message_t m;
+
+ message_init(&m);
+ message_read(MSG_NAVIGATION, &m);
+ wp = decode_navmsg(m.data);
+ if (wp->fix > fix_none &&
+ message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO)
+ {
+ const msg_satellite_t* p = m.data;
+ wp->hdop = le_readu16(p->hdop);
+ wp->hdop /= 100;
+ wp->vdop = le_readu16(p->vdop);
+ wp->vdop /= 100;
+ wp->pdop = le_readu16(p->pdop);
+ wp->pdop /= 100;
+ }
+ message_free(&m);
+ return wp;
+}
+
+//-----------------------------------------------------------------------------
+
+static void
+delbin_rw_init(const char *fname)
+{
+ message_t m;
+
+ delbin_os_ops.init(fname);
+
+ // Send a break to clear any state from a previous failure
+ message_init(&m);
+ m.size = MSG_BREAK_SIZE;
+ memset(m.data, 0, m.size);
+ message_write(MSG_BREAK, &m);
+ // get version info
+ m.size = 0;
+ message_write(MSG_VERSION, &m);
+ if (message_read(MSG_VERSION, &m)) {
+ const msg_version_t* p = m.data;
+ if (global_opts.debug_level >= DBGLVL_L)
+ warning(MYNAME ": device %s %s\n", p->product, p->firmware);
+ if (opt_long_notes) {
+ use_extended_notes = TRUE;
+ } else if (strstr(p->product, "PN-40")) {
+ // Don't know if pre-2.5 PN-40 firmware or PN-20 can handle 0xb016 message
+ use_extended_notes = p->firmware[0] > '2' ||
+ (p->firmware[0] == '2' && p->firmware[2] >= '5');
+ }
+ }
+ message_free(&m);
+}
+
+static void
+delbin_rw_deinit(void)
+{
+ delbin_os_ops.deinit();
+}
+
+static void
+delbin_read(void)
+{
+ if (doing_wpts) {
+ if (opt_getposn) {
+ waypt_add(read_position());
+ } else {
+ read_waypoints();
+ }
+ }
+ if (doing_trks) {
+ read_tracks();
+ }
+ if (doing_rtes) {
+ read_routes();
+ }
+}
+
+static void
+delbin_write(void)
+{
+ if (doing_wpts) {
+ write_waypoints();
+ }
+ if (doing_trks) {
+ write_tracks();
+ }
+ if (doing_rtes) {
+ write_routes();
+ }
+}
+
+static waypoint*
+delbin_rd_position(posn_status* status)
+{
+ waypoint* wp = read_position();
+ if (wp == NULL) {
+ status->request_terminate = 1;
+ }
+ return wp;
+}
+
+ff_vecs_t delbin_vecs = {
+ ff_type_serial,
+ FF_CAP_RW_ALL,
+ delbin_rw_init,
+ delbin_rw_init,
+ delbin_rw_deinit,
+ delbin_rw_deinit,
+ delbin_read,
+ delbin_write,
+ NULL,
+ delbin_args,
+ CET_CHARSET_LATIN1, 1,
+ { delbin_rw_init, delbin_rd_position, delbin_rw_deinit }
+};
+
+//=============================================================================
+// OS device I/O implementations
+
+#define VENDOR_ID 0x1163
+#define PRODUCT_ID 0x2020
+
+//-----------------------------------------------------------------------------
+// Windows
+#if _WIN32
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <setupapi.h>
+#include <hidsdi.h>
+
+static HANDLE hid_handle;
+
+static void
+win_os_init(const char* fname)
+{
+ GUID hid_guid;
+ HDEVINFO dev_info;
+ SP_DEVICE_INTERFACE_DATA dev_int_data;
+ PHIDP_PREPARSED_DATA hid_ppd;
+ HIDP_CAPS hid_caps;
+ const char* busy = "";
+ unsigned i;
+
+ hid_handle = INVALID_HANDLE_VALUE;
+ HidD_GetHidGuid(&hid_guid);
+ dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
+ if (dev_info == INVALID_HANDLE_VALUE) {
+ fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError());
+ }
+ dev_int_data.cbSize = sizeof(dev_int_data);
+ for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) {
+ union {
+ SP_DEVICE_INTERFACE_DETAIL_DATA detail_data;
+ char buf[300];
+ } u;
+ u.detail_data.cbSize = sizeof(u.detail_data);
+ if (SetupDiGetDeviceInterfaceDetail(dev_info,
+ &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL))
+ {
+ HANDLE h = CreateFile(u.detail_data.DevicePath,
+ FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (h != INVALID_HANDLE_VALUE) {
+ HIDD_ATTRIBUTES hid_attr;
+ hid_attr.Size = sizeof(hid_attr);
+ if (HidD_GetAttributes(h, &hid_attr) &&
+ hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID)
+ {
+ hid_handle = h;
+ break;
+ }
+ CloseHandle(h);
+ } else if (GetLastError() == ERROR_SHARING_VIOLATION &&
+ strstr(u.detail_data.DevicePath, "1163") &&
+ strstr(u.detail_data.DevicePath, "2020"))
+ {
+ busy = " (device busy?)";
+ }
+ }
+ }
+ SetupDiDestroyDeviceInfoList(dev_info);
+ if (hid_handle == INVALID_HANDLE_VALUE) {
+ fatal(MYNAME ": no DeLorme PN found%s\n", busy);
+ }
+ if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) {
+ fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError());
+ }
+ if (!HidP_GetCaps(hid_ppd, &hid_caps)) {
+ fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError());
+ }
+ // report length includes report id
+ delbin_os_packet_size = hid_caps.InputReportByteLength - 1;
+ HidD_FreePreparsedData(hid_ppd);
+}
+
+static void
+win_os_deinit(void)
+{
+ CloseHandle(hid_handle);
+}
+
+static unsigned
+win_os_packet_read(void* buf)
+{
+ unsigned n;
+ char buf1[257];
+ // first byte is report id
+ if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
+ fatal(MYNAME ": ReadFile failed %u\n", GetLastError());
+ }
+ if (n > 0) {
+ n--;
+ }
+ memcpy(buf, buf1 + 1, n);
+ return n;
+}
+
+static unsigned
+win_os_packet_write(const void* buf, unsigned size)
+{
+ unsigned n;
+ char buf1[257];
+ // first byte is report id
+ buf1[0] = 0;
+ memcpy(buf1 + 1, buf, size);
+ if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) {
+ fatal(MYNAME ": WriteFile failed %u\n", GetLastError());
+ }
+ if (n > size) {
+ n = size;
+ }
+ return n;
+}
+
+delbin_os_ops_t delbin_os_ops = {
+ win_os_init,
+ win_os_deinit,
+ win_os_packet_read,
+ win_os_packet_write
+};
+
+#endif // _WIN32
+
+//-----------------------------------------------------------------------------
+// libusb
+#if HAVE_LIBUSB
+
+#include <usb.h>
+
+static struct usb_device* usb_dev;
+static usb_dev_handle* usb_handle;
+static int endpoint_in;
+static int endpoint_out;
+
+static void
+libusb_os_init(const char* fname)
+{
+ struct usb_bus* bus;
+ const struct usb_endpoint_descriptor* endpoint_desc;
+
+ usb_init();
+ usb_find_busses();
+ usb_find_devices();
+ for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) {
+ struct usb_device* d;
+ for (d = bus->devices; d; d = d->next) {
+ if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) {
+ usb_dev = d;
+ break;
+ }
+ }
+ }
+ if (usb_dev == NULL) {
+ fatal(MYNAME ": no DeLorme PN found\n");
+ }
+ usb_handle = usb_open(usb_dev);
+ if (usb_handle == NULL) {
+ fatal(MYNAME ": %s\n", usb_strerror());
+ }
+
+ // Device has 1 configuration, 1 interface, 2 interrupt endpoints
+ if (usb_claim_interface(usb_handle, 0) < 0) {
+#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
+ if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) {
+ warning(MYNAME ": %s\n", usb_strerror());
+ }
+ if (usb_claim_interface(usb_handle, 0) < 0)
+#endif
+ {
+ const char* s = usb_strerror();
+ usb_close(usb_handle);
+ fatal(MYNAME ": %s\n", s);
+ }
+ }
+ endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint;
+ delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize;
+ endpoint_in = endpoint_desc[0].bEndpointAddress;
+ endpoint_out = endpoint_desc[1].bEndpointAddress;
+ if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) {
+ int t = endpoint_in;
+ endpoint_in = endpoint_out;
+ endpoint_out = t;
+ }
+}
+
+static void
+libusb_os_deinit(void)
+{
+ usb_release_interface(usb_handle, 0);
+ usb_close(usb_handle);
+}
+
+static unsigned
+libusb_os_packet_read(void* buf)
+{
+ int n = usb_interrupt_read(usb_handle, endpoint_in, buf, delbin_os_packet_size, 2000);
+ if (n < 0) {
+ fatal(MYNAME ": %s\n", usb_strerror());
+ }
+ return n;
+}
+
+static unsigned
+libusb_os_packet_write(const void* buf, unsigned size)
+{
+ int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000);
+ if (n < 0) {
+ fatal(MYNAME ": %s\n", usb_strerror());
+ }
+ return n;
+}
+
+#if __linux
+static const delbin_os_ops_t libusb_os_ops =
+#else
+delbin_os_ops_t delbin_os_ops =
+#endif
+{
+ libusb_os_init,
+ libusb_os_deinit,
+ libusb_os_packet_read,
+ libusb_os_packet_write
+};
+
+#endif // HAVE_LIBUSB
+
+//-----------------------------------------------------------------------------
+// Linux
+#if __linux
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/hiddev.h>
+#include <linux/hidraw.h>
+
+static int fd_hidraw;
+static int fd_hiddev;
+
+static int linuxhid_os_init_status;
+
+static void
+linuxhid_os_init(const char* fname)
+{
+ struct hidraw_devinfo info;
+ struct hiddev_report_info rinfo;
+ struct hiddev_field_info finfo;
+ DIR* dir = NULL;
+ struct dirent* d;
+
+ fd_hidraw = fd_hiddev = -1;
+ if (fname && memcmp(fname, "hid:", 4) == 0) {
+ const char* raw_name = fname + 4;
+ const char* dev_name = strchr(raw_name, ',');
+ if (dev_name == NULL) {
+ fatal(MYNAME ": missing hiddev\n");
+ }
+ fd_hidraw = open(raw_name, O_RDONLY);
+ if (fd_hidraw < 0) {
+ fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno));
+ }
+ dev_name++;
+ fd_hiddev = open(dev_name, O_WRONLY);
+ if (fd_hiddev < 0) {
+ fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno));
+ }
+ } else {
+ dir = opendir("/dev");
+ }
+ while (dir && (d = readdir(dir)) != NULL) {
+ if (strncmp(d->d_name, "hidraw", 6) == 0) {
+ int fd1, fd2;
+ char raw_name[32];
+ char dev_name[32];
+ sprintf(raw_name, "/dev/%s", d->d_name);
+ fd1 = open(raw_name, O_RDONLY);
+ if (fd1 < 0) {
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": %s: %s\n", raw_name, strerror(errno));
+ continue;
+ }
+ sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
+ fd2 = open(dev_name, O_WRONLY);
+ if (fd2 < 0 && errno == ENOENT) {
+ sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1);
+ fd2 = open(dev_name, O_WRONLY);
+ }
+ if (fd2 < 0) {
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": %s: %s\n", dev_name, strerror(errno));
+ close(fd1);
+ continue;
+ }
+ if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 &&
+ info.vendor == VENDOR_ID && info.product == PRODUCT_ID)
+ {
+ fd_hidraw = fd1;
+ fd_hiddev = fd2;
+ break;
+ }
+ close(fd1);
+ close(fd2);
+ }
+ }
+ if (dir) {
+ closedir(dir);
+ }
+ if (fd_hidraw < 0) {
+ if (linuxhid_os_init_status == 0)
+ fatal(MYNAME ": no DeLorme PN found\n");
+ return;
+ }
+ rinfo.report_type = HID_REPORT_TYPE_INPUT;
+ rinfo.report_id = HID_REPORT_ID_FIRST;
+ if (ioctl(fd_hiddev, HIDIOCGREPORTINFO, &rinfo) < 0) {
+ warning(MYNAME ": HIDIOCGREPORTINFO: %s\n", strerror(errno));
+ if (linuxhid_os_init_status == 0)
+ exit(1);
+ return;
+ }
+ finfo.report_type = rinfo.report_type;
+ finfo.report_id = rinfo.report_id;
+ finfo.field_index = 0;
+ if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) {
+ warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno));
+ if (linuxhid_os_init_status == 0)
+ exit(1);
+ return;
+ }
+ delbin_os_packet_size = finfo.maxusage;
+ linuxhid_os_init_status = 0;
+}
+
+static void
+linuxhid_os_deinit(void)
+{
+ close(fd_hidraw);
+ close(fd_hiddev);
+}
+
+static unsigned
+linuxhid_os_packet_read(void* buf)
+{
+ int n = read(fd_hidraw, buf, delbin_os_packet_size);
+ if (n < 0) {
+ fatal(MYNAME ": %s\n", strerror(errno));
+ }
+ return n;
+}
+
+static unsigned
+linuxhid_os_packet_write(const void* buf, unsigned size)
+{
+ struct hiddev_usage_ref_multi urefm;
+ struct hiddev_report_info rinfo;
+ const gbuint8* p = buf;
+ unsigned i;
+
+ for (i = 0; i < size; i++) {
+ urefm.values[i] = p[i];
+ }
+ urefm.num_values = size;
+ memset(&urefm.uref, 0, sizeof(urefm.uref));
+ urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT;
+ if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) {
+ fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno));
+ }
+ memset(&rinfo, 0, sizeof(rinfo));
+ rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
+ if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) {
+ fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno));
+ }
+ return size;
+}
+
+static const delbin_os_ops_t linuxhid_os_ops = {
+ linuxhid_os_init,
+ linuxhid_os_deinit,
+ linuxhid_os_packet_read,
+ linuxhid_os_packet_write
+};
+
+static void linux_os_init(const char* fname);
+
+delbin_os_ops_t delbin_os_ops = {
+ linux_os_init,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+linux_os_init(const char* fname)
+{
+ // tell linuxhid_os_init not to exit
+ linuxhid_os_init_status = 1;
+ linuxhid_os_init(fname);
+ if (linuxhid_os_init_status == 0) {
+ delbin_os_ops = linuxhid_os_ops;
+ } else {
+#if HAVE_LIBUSB
+ if (global_opts.debug_level >= DBGLVL_M)
+ warning(MYNAME ": HID init failed, falling back to libusb\n");
+ delbin_os_ops = libusb_os_ops;
+ delbin_os_ops.init(fname);
+#else
+ fatal(MYNAME ": no DeLorme PN found\n");
+#endif
+ }
+}
+
+#endif // __linux
+
+//-----------------------------------------------------------------------------
+// stubs
+#if !(_WIN32 || __linux || HAVE_LIBUSB)
+static void
+stub_os_init(const char* fname)
+{
+ fatal(MYNAME ": OS not supported\n");
+}
+static void
+stub_os_deinit(void)
+{
+}
+static unsigned
+stub_os_packet_read(void* buf)
+{
+ return 0;
+}
+static unsigned
+stub_os_packet_write(const void* buf, unsigned size)
+{
+ return 0;
+}
+delbin_os_ops_t delbin_os_ops = {
+ stub_os_init,
+ stub_os_deinit,
+ stub_os_packet_read,
+ stub_os_packet_write
+};
+#endif
+// end OS device I/O implementations section
+//=============================================================================
+
+static const int track_color_bgr[] = {
+ 0x0000ff, // red
+ 0x00ffff, // yellow
+ 0x008000, // green
+ 0xff0000, // blue
+ 0x808080, // gray
+ 0xffffff, // white
+ 0, // black
+ 0xffff00, // cyan
+ 0xff00ff, // magenta
+ 0x00a5ff, // orange
+ 0x82004b, // indigo
+ 0xeea5ee // violet
+};
+
+static int track_color(unsigned i)
+{
+ int bgr = -1;
+ if (i < sizeofarray(track_color_bgr)) {
+ bgr = track_color_bgr[i];
+ }
+ return bgr;
+}
+
+static unsigned track_color_index(int bgr)
+{
+ unsigned i = sizeofarray(track_color_bgr);
+ do {
+ i--;
+ } while (i != 0 && track_color_bgr[i] != bgr);
+ return i;
+}
+
+static const char* const waypoint_symbol_name[] = {
+ // 0
+ "Red Map Pin",
+ "Dark Red Map Pin",
+ "Yellow Map Pin",
+ "Dark Yellow Map Pin",
+ "Green Map Pin",
+ "Dark Green Map Pin",
+ "Turquoise Map Pin",
+ "Dark Turquoise Map Pin",
+ "Blue Map Pin",
+ "Dark Blue Map Pin",
+ // 10
+ "Gray Map Pin",
+ "Dark Gray Map Pin",
+ "Red Flag",
+ "Dark Red Flag",
+ "Yellow Flag",
+ "Dark Yellow Flag",
+ "Green Flag",
+ "Dark Green Flag",
+ "Turquoise Flag",
+ "Dark Turquoise Flag",
+ // 20
+ "Blue Flag",
+ "Dark Blue Flag",
+ "Gray Flag",
+ "Dark Gray Flag",
+ "Red Dot",
+ "Dark Red Dot",
+ "Yellow Dot",
+ "Dark Yellow Dot",
+ "Green Dot",
+ "Dark Green Dot",
+ // 30
+ "Turquoise Dot",
+ "Dark Turquoise Dot",
+ "Blue Dot",
+ "Dark Blue Dot",
+ "Gray Dot",
+ "Dark Gray Dot",
+ "Small Red Dot",
+ "Small Dark Red Dot",
+ "Small Yellow Dot",
+ "Small Dark Yellow Dot",
+ // 40
+ "Small Green Dot",
+ "Small Dark Green Dot",
+ "Small Turquoise Dot",
+ "Small Dark Turquoise Dot",
+ "Small Blue Dot",
+ "Small Dark Blue Dot",
+ "Small Gray Dot",
+ "Small Dark Gray Dot",
+ "Arrow Up",
+ "Arrow Down",
+ // 50
+ "Arrow Left",
+ "Arrow Right",
+ "Arrow Up Left",
+ "Arrow Up Right",
+ "Arrow Down Left",
+ "Arrow Dow Right",
+ "Green Star",
+ "Yellow Square",
+ "Red X",
+ "Turquoise Circle",
+ // 60
+ "Purple Triangle",
+ "American Flag",
+ "Stop",
+ "Parking",
+ "First Aid",
+ "Dining",
+ "Railroad Crossing",
+ "Heliport",
+ "Restroom",
+ "Information",
+ // 70
+ "Diver Down",
+ "Exit",
+ "Health Facility",
+ "Police",
+ "Post Office",
+ "Mining",
+ "Danger",
+ "Money",
+ "Exclamation",
+ "Car",
+ // 80
+ "Jeep",
+ "Truck",
+ "Tow Truck",
+ "Motor Home",
+ "School Bus",
+ "Four-wheeler",
+ "Snowmobile",
+ "Sailboat",
+ "Powerboat",
+ "Boat Launch",
+ // 90
+ "Anchor",
+ "Buoy",
+ "Shipwreck",
+ "Glider Area",
+ "Private Airport",
+ "Public Airport",
+ "Military Airport",
+ "Military Base",
+ "House",
+ "Church",
+ // 100
+ "Building",
+ "School",
+ "Lighthouse",
+ "Bridge",
+ "Radio Tower",
+ "Dam",
+ "Tunnel",
+ "Toll Booth",
+ "Gas Station",
+ "Lodging",
+ // 110
+ "Telephone",
+ "Traffic Light",
+ "Fire Hydrant",
+ "Tombstone",
+ "Picnic Table",
+ "Tent",
+ "Shelter",
+ "Camper",
+ "Fire",
+ "Shower",
+ // 120
+ "Drinking Water",
+ "Binoculars",
+ "Camera",
+ "Geocache",
+ "Geocache Found",
+ "Fishing Pole",
+ "Ice Fishing Trap Set",
+ "Ice Fishing Trap Up",
+ "Moose",
+ "Deer",
+ // 130
+ "Bear",
+ "Bird",
+ "Duck",
+ "Fish",
+ "Deer Tracks",
+ "Animal Tracks",
+ "Bird Tracks",
+ "Birch Tree",
+ "Evergreen Tree",
+ "Deciduous Tree",
+ // 140
+ "Flower Garden",
+ "Mountain",
+ "Cave",
+ "Beach",
+ "Hiking",
+ "Swimming",
+ "Bicycling",
+ "Kayaking",
+ "Canoeing",
+ "Water Skiing",
+ // 150
+ "Cross-country Skiing",
+ "Downhill Skiing",
+ "Ice Skating",
+ "Dogsledding",
+ "Shooting",
+ "Golf Course",
+ "Ballpark",
+ // 157-182 added in PN-40 2.5 firmware
+ "Cache Found",
+ "Didn't Find It",
+ "My Cache",
+ // 160
+ "Traditional Cache",
+ "Multi-Cache",
+ "Unknown Cache",
+ "Letterbox Hybrid",
+ "Whereigo Cache",
+ "Event Cache",
+ "Mega-Event Cache",
+ "Cache In Trash Out Event",
+ "EarthCache",
+ "Virtual Cache",
+ // 170
+ "Webcam Cache",
+ "Waymark",
+ "NGS Benchmark",
+ "Write Note",
+ "Needs Maintenance",
+ "Final Location",
+ "Parking Area",
+ "Question to Answer",
+ "Reference Point",
+ "Stages of a Multicache",
+ // 180
+ "Trailhead",
+ "Temporarily Disable Listing",
+ "Enable Listing"
+};
+
+static const char*
+waypoint_symbol(unsigned i)
+{
+ const char* p = NULL;
+ if (i < sizeofarray(waypoint_symbol_name)) {
+ p = waypoint_symbol_name[i];
+ }
+ return p;
+}
+
+static unsigned
+waypoint_symbol_index(const char* name)
+{
+ static unsigned last_result;
+ static char last_name[32];
+ unsigned i = last_result;
+
+ if (strncmp(name, last_name, sizeof(last_name)) != 0) {
+ i = sizeofarray(waypoint_symbol_name);
+ do {
+ i--;
+ } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0);
+ strncpy(last_name, name, sizeof(last_name));
+ last_result = i;
+ }
+ return i;
+}
+
+// vi: ts=4 sw=4 noexpandtab